---
title: Range Calendar
description: A component that allows users to select a range of dates.
links:
  - label: Aria docs
    href: https://react-spectrum.adobe.com/react-aria/RangeCalendar.html
  - label: "@internationalized/date"
    href: https://react-spectrum.adobe.com/internationalized/date/index.html
  - label: Report an issue
    href: https://github.com/mehdibha/dotUI/issues/new/choose
  - label: Edit this page
    href: https://github.com/mehdibha/dotUI/tree/main/content/components/dates/range-calendar.mdx?plain=1
---

<ComponentPreview
  name="calendar/demos/range-calendar/default"
  preview={`<RangeCalendar aria-label="Trip dates" />`}
/>

## Installation

```package-install
npx shadcn@latest add @dotui/range-calendar
```

## Usage

Use `RangeCalendar` to allow users to select a contiguous or non-contiguous range of dates.

```tsx
import { parseDate } from "@internationalized/date";

import { RangeCalendar } from "@/components/core/calendar";

<RangeCalendar
  aria-label="Trip dates"
  defaultValue={{
    start: parseDate("2020-02-03"),
    end: parseDate("2020-02-12"),
  }}
/>;
```

```tsx
import { parseDate } from "@internationalized/date";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";

import { Button } from "@/components/core/button";
import {
  CalendarCell,
  CalendarGrid,
  CalendarGridBody,
  CalendarGridHeader,
  CalendarHeader,
  CalendarHeaderCell,
  RangeCalendarRoot,
} from "@/components/core/calendar";
import { Heading } from "@/registry/ui/default/core/heading";

<RangeCalendarRoot
  aria-label="Trip dates"
  defaultValue={{
    start: parseDate("2020-02-03"),
    end: parseDate("2020-02-12"),
  }}
>
  <CalendarHeader>
    <Button slot="previous">
      <ChevronLeftIcon />
    </Button>
    <Heading />
    <Button slot="next">
      <ChevronRightIcon />
    </Button>
  </CalendarHeader>
  <CalendarGrid>
    <CalendarGridHeader>
      {(day) => <CalendarHeaderCell>{day}</CalendarHeaderCell>}
    </CalendarGridHeader>
    <CalendarGridBody>
      {(date) => <CalendarCell date={date} />}
    </CalendarGridBody>
  </CalendarGrid>
</RangeCalendarRoot>;
```

### Best practices

- An `aria-label` must be provided to the `RangeCalendar` for accessibility. If it is labeled by a separate element, an `aria-labelledby` prop must be provided using the id of the labeling element instead.

## Uncontrolled

An initial, uncontrolled value can be provided to the `RangeCalendar` using the `defaultValue` prop.

<ComponentPreview
  name="calendar/demos/range-calendar/uncontrolled"
  preview={`<RangeCalendar
    aria-label="Trip dates"
    defaultValue={{
      start: parseDate("2020-02-03"),
      end: parseDate("2020-02-12"),
    }}
  />`}
/>

## Controlled

The Range Calendar component can be controlled by passing the `value` and `onChange` props.

<ComponentPreview
  name="calendar/demos/range-calendar/controlled"
  preview={`const [value, setValue] = React.useState<DateRange>({
    start: parseDate("2020-02-03"),
    end: parseDate("2020-02-12"),
  });
  return <RangeCalendar aria-label="Trip dates" value={value} onChange={setValue} />`}
/>

## Validation

### Min and max values

By default, Calendar allows selecting any date. The minValue and maxValue props can also be used to prevent the user from selecting dates outside a certain range.

This example only accepts dates after today.

<ComponentPreview
  name="calendar/demos/range-calendar/validation"
  preview={`<RangeCalendar aria-label="Trip dates" minValue={today(getLocalTimeZone())} />`}
/>

### Unavailable dates

Calendar supports marking certain dates as unavailable. These dates cannot be selected by the user and are displayed with a crossed out appearance. The `isDateUnavailable` prop accepts a callback that is called to evaluate whether each visible date is unavailable.

This example includes multiple unavailable date ranges, e.g. dates when a rental house is not available. The `minValue` prop is also used to prevent selecting dates before today.

{/* prettier-ignore-start */}

<ComponentPreview
  name="calendar/demos/range-calendar/unvailable-dates"
  preview={`const now = today(getLocalTimeZone());
  const disabledRanges = [
    [now, now.add({ days: 5 })],
    [now.add({ days: 14 }), now.add({ days: 16 })],
    [now.add({ days: 23 }), now.add({ days: 24 })],
  ];

  const isDateUnavailable = (date: DateValue) =>
    disabledRanges.some(
      (interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0
    );

  return (
    <RangeCalendar
      aria-label="Trip dates"
      minValue={today(getLocalTimeZone())}
      isDateUnavailable={isDateUnavailable}
    />
  );`}
/>

{/* prettier-ignore-end */}

### Non-contiguous ranges

The `allowsNonContiguousRanges` prop enables a range to be selected even if there are unavailable dates in the middle.

This example prevents selecting weekends, but allows selecting ranges that span multiple weeks.

<ComponentPreview
  name="calendar/demos/range-calendar/non-contiguous-ranges"
  preview={`const now = today(getLocalTimeZone());
  const disabledRanges = [
    [now, now.add({ days: 5 })],
    [now.add({ days: 14 }), now.add({ days: 16 })],
    [now.add({ days: 23 }), now.add({ days: 24 })],
  ];
  const isDateUnavailable = (date: DateValue) =>
    disabledRanges.some(
      (interval) => date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0
    );
  return (
    <RangeCalendar
      aria-label="Trip dates"
      minValue={today(getLocalTimeZone())}
      isDateUnavailable={isDateUnavailable}
      allowsNonContiguousRanges
    />
  );`}
/>

### Error message

`RangeCalendar` tries to avoid allowing the user to select invalid dates in the first place (see [Min and max values](/docs/components/date-and-time/calendar#min-and-max-values) and [Unavailable dates](/docs/components/date-and-time/calendar#unavailable-dates)). However, if according to application logic a selected date is invalid, Use `isInvalid` and `errorMessage` props.

This example validates that the selected date range is a maximum of 1 week in duration.

<ComponentPreview
  name="calendar/demos/range-calendar/error-message"
  preview={`const [range, setRange] = React.useState({
    start: today(getLocalTimeZone()),
    end: today(getLocalTimeZone()).add({ weeks: 1, days: 3 }),
  });
  const isInvalid = range.end.compare(range.start) > 7;
  return (
    <RangeCalendar
      aria-label="Trip dates"
      value={range}
      onChange={setRange}
      isInvalid={isInvalid}
      errorMessage={isInvalid ? "Maximum stay duration is 1 week" : undefined}
    />
  );`}
/>

## Options

### Visible months

By default, the `RangeCalendar` displays a single month. The `visibleMonths` prop allows displaying up to 3 months at a time.

<ComponentPreview
  name="calendar/demos/range-calendar/visible-months"
  preview={`<RangeCalendar aria-label="Trip dates" visibleMonths={2} />`}
/>

### Page behaviour

The `pageBehavior` prop allows you to control how the range-calendar navigates between months.

<ComponentPreview
  name="calendar/demos/range-calendar/page-behaviour"
  preview={`<RangeCalendar aria-label="Trip dates" visibleMonths={2} pageBehavior="single" />`}
/>

### Disabled

The `isDisabled` boolean prop makes the `RangeCalendar` disabled. cells cannot be focused or selected.

<ComponentPreview
  name="calendar/demos/range-calendar/disabled"
  preview={`<RangeCalendar aria-label="Trip dates" isDisabled />`}
/>

### Read only

The `isReadOnly` boolean prop makes the `RangeCalendar`'s value immutable. Unlike `isDisabled`, the `RangeCalendar` remains focusable.

<ComponentPreview
  name="calendar/demos/range-calendar/read-only"
  preview={`<RangeCalendar
    aria-label="Trip dates"
    isReadOnly
    value={{
      start: parseDate("2020-02-03"),
      end: parseDate("2020-02-12"),
    }}
  />`}
/>

## Composition

If you need to customize things further, you can drop down to the composition level.

<ComponentPreview
  suspense
  name="calendar/demos/range-calendar/composition"
  preview={`<RangeCalendarRoot aria-label="Trip dates">
    <CalendarHeader>
      <Button slot="previous">
        <ChevronLeftIcon />
      </Button>
      <Heading />
      <Button slot="next">
        <ChevronRightIcon />
      </Button>
    </CalendarHeader>
    <CalendarGrid>
      <CalendarGridHeader>
        {(day) => <CalendarHeaderCell>{day}</CalendarHeaderCell>}
      </CalendarGridHeader>
      <CalendarGridBody>
        {(date) => <CalendarCell date={date} />}
      </CalendarGridBody>
    </CalendarGrid>
  </RangeCalendarRoot>`}
/>

## API Reference

| Prop                  | Type                                                                                                       | Default     | Description                                                                                                                                         |
| --------------------- | ---------------------------------------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `visibleMonths`       | `number`                                                                                                   | `1`         | The number of months to display at once. Up to 3 months are supported.                                                                              |
| `minValue`            | `DateValue`                                                                                                | -           | The minimum allowed date that a user may select.                                                                                                    |
| `maxValue`            | `DateValue`                                                                                                | -           | The maximum allowed date that a user may select.                                                                                                    |
| `isDateUnavailable`   | `(date: DateValue) => boolean`                                                                             | -           | Callback that is called for each date of the range calendar. If it returns true, then the date is unavailable.                                      |
| `isDisabled`          | `boolean`                                                                                                  | `false`     | Whether the range calendar is disabled.                                                                                                             |
| `isReadOnly`          | `boolean`                                                                                                  | `false`     | Whether the range calendar value is immutable.                                                                                                      |
| `autoFocus`           | `boolean`                                                                                                  | `false`     | Whether to automatically focus the range calendar when it mounts.                                                                                   |
| `focusedValue`        | `DateValue`                                                                                                | -           | Controls the currently focused date within the range calendar.                                                                                      |
| `defaultFocusedValue` | `DateValue`                                                                                                | -           | The date that is focused when the range calendar first mounts (uncountrolled).                                                                      |
| `isInvalid`           | `boolean`                                                                                                  | -           | Whether the current selection is invalid according to application logic.                                                                            |
| `pageBehavior`        | `'single' \| 'visible'`                                                                                    | `'visible'` | Controls the behavior of paging. Pagination either works by advancing the visible page by visibleDuration (default) or one unit of visibleDuration. |
| `value`               | `RangeValue<DateValue> \| null`                                                                            | -           | The current value (controlled).                                                                                                                     |
| `defaultValue`        | `RangeValue<DateValue> \| null`                                                                            | -           | The default value (uncontrolled).                                                                                                                   |
| `children`            | `ReactNode \| (values: RangeCalendarRenderProps & {defaultChildren: ReactNode \| undefined}) => ReactNode` | -           | The children of the component. A function may be provided to alter the children based on component state.                                           |
| `className`           | `string`                                                                                                   | -           | The CSS className for the element.                                                                                                                  |
| `style`               | `CSSProperties \| (values: RangeCalendarRenderProps & {defaultStyle: CSSProperties}) => CSSProperties`     | -           | The inline style for the element. A function may be provided to compute the style based on component state.                                         |

| Event           | Type                                                      | Description                                           |
| --------------- | --------------------------------------------------------- | ----------------------------------------------------- |
| `onFocusChange` | `(date: CalendarDate) => void`                            | Handler that is called when the focused date changes. |
| `onChange`      | `(value: RangeValue<MappedDateValue<DateValue>>) => void` | Handler that is called when the value changes.        |

## Accessibility

### Keyboard interactions

| Key             | Description                                                       |
| --------------- | ----------------------------------------------------------------- |
| `Tab`           | Moves focus to the next focusable item in the range calendar.     |
| `Shift+Tab`     | Moves focus to the previous focusable item in the range calendar. |
| `ArrowRight`    | Moves focus to the next day.                                      |
| `ArrowLeft`     | Moves focus to the previous day.                                  |
| `ArrowDown`     | Moves focus to the same day of the week in the next week.         |
| `ArrowUp`       | Moves focus to the same day of the week in the previous week.     |
| `Space` `Enter` | Selects the focused date.                                         |
